module AboutYou
  module SDK
    module Model
      ###
      # This Class represents a basket model
      ###
      class Basket
        # array of items in the basket
        attr_accessor :items
        # errors of the basket
        attr_accessor :errors
        # Integer count of unique variants
        attr_accessor :unique_variant_count
        # Array of products
        attr_accessor :products
        # Integer containing the total price of the basket
        attr_accessor :total_price
        # Integer containing the total net woth of the basket
        attr_accessor :total_net
        # Integer containing the total vat woth of the basket
        attr_accessor :total_vat
        # Integer containing the total amount of the basket
        attr_accessor :total_amount
        # Array containing the deleted items in the basket
        attr_accessor :deleted_items
        # Array containing the updated items in the basket
        attr_accessor :updated_items

        ###
        # This method is used for creating an instance of this class by a json_object.
        #
        # * *Args*    :
        #   - +json_object+ -> the json_object received from the api
        #   - +factory+ -> instance of AboutYou::SDK::Factory::DefaultModelFactory
        #
        # * *Returns* :
        #   - Instance of AboutYou::SDK::Model::Basket
        ###
        def self.create_from_json(json_object, factory)
          basket = new
          basket.errors = {}
          basket.deleted_items = {}
          basket.updated_items = {}
          basket.total_price  = json_object['total_price']
          basket.total_net    = json_object['total_net']
          basket.total_vat    = json_object['total_vat']
          basket.parse_items(json_object, factory)
          basket.total_amount = items.count

          basket
        end

        ###
        # This method checks if there are errors in the basket set
        #
        # * *Returns* :
        #   - Boolean determining whether there are errors or not
        ###
        def errors?
          errors.count > 0
        end

        ###
        # This method gets an item from the basket for a given item id
        #
        # * *Args*    :
        #   - +item_id+ -> the id used for searching in the basket
        #
        # * *Returns* :
        #   - instance of AboutYou::SDK::Model::BasketItem or null if not found
        ###
        def item(item_id)
          return items[item_id] if items.key?(item_id)
        end

        ###
        # This methods creates a Hash containing pairs of 
        # unique_item_key => instance of AboutYou::SDK::Model::BasketItem
        # It only contains 1 entry per unique_item_key and will increase the amount
        # if more items are given
        #
        # * *Returns* :
        #   - Hash containing pairs of 
        #     unique_item_key => instance of AboutYou::SDK::Model::BasketItem
        ###
        def collected_items
          items = self.items
          items_merged = {}
          items.each do |item|
            key = item.unique_key
            if items_merged.key?(key)
              amount = items_merged[key]['amount'] + 1
              items_merged[key] = {
                'item' => item,
                'price' => item.total_price * amount,
                'amount' => amount
              }
            else
              items_merged[key] = {
                'item' => item,
                'price' => item.total_price,
                'amount' => 1
              }
            end
          end

          items_merged
        end

        ###
        # This method builds the order lines for the update query
        #
        # * *Returns* :
        #   - Array containing either Hashes with pairs of 'delete' => item_id
        #     or instances of AboutYou::SDK::Model::BasketItem
        ###
        def order_lines_array
          order_lines = []

          deleted_items.uniq.each do |itemId|
            order_lines.push('delete' => itemId)
          end

          updatedItems.each do |item|
            order_lines.push(item)
          end

          order_lines
        end

        ###
        # This method is used for parsing the items in the basket
        #
        # * *Args* :
        #   - +json_object+ -> the json object received from the api
        #   - +factory+ -> Instance of AboutYou::SDK::Factory::DefaultModelFactory
        #
        # * *Returns* :
        #   - Instance of AboutYou::SDK::Model::Basket
        ###
        def parse_items(json_object, factory)
          products = {}

          json_object['products'].each do |key, json_product|
            products[key] = factory.create_product(json_product)
          end if json_object['products']

          self.products = products
          vids = []

          json_object['order_lines'].each do |key, json_item|
            if json_item['set_items']
              item = factory.create_basket_set(json_item, products)
            else
              vids.push(json_item['variantId'])
              item = factory.create_basket_item(json_item, products)
            end

            if item.errors?
              errors[key] = item
            else
              items[item.id] = item
            end
          end if json_object['order_lines']

          vids = vids.uniq
          self.uniqueVariantCount = vids.count

          self
        end

        ###
        # This method is used for deleting an item from the basket
        #
        # * *Args* :
        #   - +item_id+ -> the item_id for which the item should be deleted
        #
        # * *Returns* :
        #   - Instance of AboutYou::SDK::Model::Basket
        ###
        def delete_item(item_id)
          deleted_items[item_id] = item_id

          self
        end

        ###
        # This method is used for deleting items from the basket
        #
        # * *Args* :
        #   - +item_ids+ -> Array containing item ids for which items should be deleted
        #
        # * *Returns* :
        #   - Instance of AboutYou::SDK::Model::Basket
        ###
        def delete_items(item_ids)
          item_ids.each do |item_id|
            deleted_items[item_id] = item_id
          end

          self
        end

        ###
        # This method is used for deleting all items from the basket
        #
        # * *Returns* :
        #   - Instance of AboutYou::SDK::Model::Basket
        ###
        def delete_all_items
          items = self.items

          return self unless items.empty?
          ids = []
          items.each do |item|
            ids.push = item.id
          end
          delete_items(ids)

          self
        end

        ###
        # This method is used for updating an item in the basket
        #
        # * *Args* :
        #   - +basket_item+ -> instance of AboutYou::SDK::Model::BasketItem
        #
        # * *Returns* :
        #   - Instance of AboutYou::SDK::Model::Basket
        ###
        def update_item(basket_item)
          item = {
            'id' => basket_item.id,
            'variant_id' => basket_item.variant_id,
            'app_id' => basket_item.app_id
          }
          add_data = basket_item.additional_data
          if add_data
            check_additional_data(add_data)
            item['additional_data'] = add_data
          end
          updated_items[basket_item.id] = item

          self
        end

        ###
        # This method is used for updating an item set in the basket
        #
        # * *Args* :
        #   - +basket_item+ -> instance of AboutYou::SDK::Model::BasketSet
        #
        # * *Returns* :
        #   - Instance of AboutYou::SDK::Model::Basket
        ###
        def update_item_set(basket_set)
          items = basket_set.items

          fail 'InvalidArgumentException! BasketSet
            needs at least one item' if items.empty?

          item_set = []
          items.each do |sub_item|
            item = {
              'variant_id' => sub_item.variant_id,
              'app_id' => sub_item.app_id
            }
            add_data = sub_item.additional_data
            if add_data
              check_additional_data(add_data)
              item['additional_data'] = add_data
            end
            item_set.push = item
          end

          updated_items[basket_set.id] = {
            'id' => basket_set.id,
            'additional_data' => basket_set.additional_data,
            'set_items' => item_set
          }

          self
        end

        ###
        # checks if certain additional_data is valid to set for a basket or not
        #
        # * *Args*    :
        #   - +add_data+ -> the desired data to check
        #   - +_img_url_required+ -> unused operator
        #
        # * *Fails* :
        #   - if add_data doesnt have a description
        #   - if add_data doesnt have valid internal infos
        ###
        def check_additional_data(add_data = nil, _img_url_required = false)
          fail 'InvalidArgumentException! description is required
            in additional data' if add_data && !add_data.key?('description')
          fail 'InvalidArgumentException! internal_infos must be an array' if
          add_data.key?('internal_infos') &&
          !add_data['internal_infos'].is_a?(Array)
        end
      end
    end
  end
end
